/**
* file name  : PairAllele.java
* authors    : Luciano Xumerle
* created    : mer 30 mar 2005 09:10:25 CEST
*
* Copyright (c) 2005 Luciano Xumerle. All rights reserved.
*
* This file is part of gephinfo.
*
* gephinfo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package it.ciano.jenoware;

import java.util.Arrays;

/**
 * The class stores and manages two Alleles object.
 *
 * @author Luciano Xumerle
 * @version 0.0.1
 */
public class PairAllele implements Comparable
{
    // the two allele
    private Allele[] _allele;
    // store the first allele to mantain the order if required
    private Allele theFirstInInput;
    // the PaiAllele frequency
    private double genotypeFrequency;
    // true if the two alleles are valid
    private boolean _valid;
    // true if the two alleles are equal
    private boolean _homo;
    // true if allele order is not lexical or numeric. It's used with haplotypes!
    private boolean _mantainAllelesOrder;


    /**
     * Class constructor for a not valid PairAllele.
     */
    public PairAllele()
    {
        this ( new Allele(), new Allele(), 1, false );
    }

    /**
     * Class Constructor.
     *
     * @param allele1 the first int allele.
     * @param allele2 the second int allele.
     */
    public PairAllele ( int allele1, int allele2 )
    {
        this ( new Allele ( allele1 ), new Allele ( allele2 ), 1, false );
    }

    /**
     * Class Constructor.
     *
     * @param allele1 the first allele.
     * @param allele2 the second allele.
     */
    public PairAllele ( String allele1, String allele2 )
    {
        this ( new Allele ( allele1 ), new Allele ( allele2 ), 1, false );
    }

    /**
    * Class Constructor.
    *
    * @param allele1 the first allele.
    * @param allele2 the second allele.
    */
    public PairAllele ( Allele allele1, Allele allele2 )
    {
        this ( allele1, allele2, 1.0, false );
    }

    /**
     * Class Constructor.
     *
     *
     * @param allele1 the first allele.
     * @param allele2 the second allele.
     * @param freq the frequency (or probability for this pair)
     * @param useInveseOrder the order of allele is not lexicographical.
     * It is set to true only if the second allele is major than the first.
     */
    public PairAllele ( Allele allele1, Allele allele2, double freq, boolean useInveseOrder )
    {
        _allele = new Allele[ 2 ];
        _valid = false;
        _homo = false;
        _mantainAllelesOrder = false;
        if ( allele1 != null && allele2 != null
                && allele1.isValid() && allele2.isValid() )
        {
            theFirstInInput=allele1;
            _valid = true;
            _allele[ 0 ] = allele1;
            _allele[ 1 ] = allele2;
            if ( _allele[ 1 ].equals ( _allele[ 0 ] ) )
            {
                _homo = true;
                _allele[ 0 ] = _allele[ 1 ];
            }
            else
                Arrays.sort ( _allele );
        }
        else
        {
            _allele[ 0 ] = new Allele ( "" );
            _allele[ 1 ] = _allele[ 0 ];
            theFirstInInput=_allele[ 0 ];
        }
        setFrequency ( freq );
        setMantainAllelesOrder ( useInveseOrder );
    }

    /**
     * Returns true if the two stored Allele objects are valid.
     *
     * @return true or false.
     */
    public boolean isValid()
    {
        return _valid;
    }

    /**
     * Returns true if the two stored Allele objects are numeric.
     *
     * @return true or false.
     */
    public boolean isNumeric()
    {
        return ( _allele[ 0 ].isNumeric() && _allele[ 1 ].isNumeric() );
    }

    /**
     * Returns true if the two stored Allele objects are equal.
     *
     * @return true or false.
     */
    public boolean isHomozigote()
    {
        return _homo;
    }

    /**
     * Returns true if the two stored Allele objects are not equal.
     *
     * @return true or false.
     */
    public boolean isHeterozygote()
    {
        return ( !_homo );
    }

    /**
     * Returns true if the alleles must be in reverse order.
     *
     * @return true if the alleles must be in reverse order.
     */
    public boolean isMantainAllelesOrder()
    {
        return _mantainAllelesOrder;
    }

    /**
     * Set the order of alleles. Default order is lexical or numeric,
     * the reverse order is used when the PairAllele is part
     * of a haplotype.
     *
     * @param useInveseOrder It's true if the user wants reversed order of alleles.
     * It is set to true only if the second allele must be the first.
     */
    public void setMantainAllelesOrder ( boolean useInveseOrder )
    {
        _mantainAllelesOrder=useInveseOrder;
    }

    /**
     * Sets the genotype frequency.
     *
     * @param freq the frequency.
     */
    public void setFrequency ( double freq )
    {
        if ( freq>=0 && freq<=1 )
            genotypeFrequency=freq;
        else
            genotypeFrequency=1;
    }

    /**
     * Returns the frequency value for the Genotype (-1 if non set).
     *
     * @return the frequency value for the Genotype (-1 if non set).
     */
    public double getFrequency()
    {
        return  genotypeFrequency;
    }

    /**
     * Returns Allele x (x=[1,2]).
     *
     * @param i number (1 or 2) of the allele.
     * @return the Allele object.
     */
    public Allele getAllele ( int i )
    {
        if ( _mantainAllelesOrder && theFirstInInput.equals ( _allele[ 1 ] ) )
        {
            if ( i == 1 )
                return _allele[ 1 ];
            if ( i == 2 )
                return _allele[ 0 ];
        }
        if ( i == 1 )
            return _allele[ 0 ];
        if ( i == 2 )
            return _allele[ 1 ];
        return null;
    }

    /**
     * Returns the value of the first allele.
     *
     * @return String with the first allele
     */
    public String getAll1()
    {
        return getAllele ( 1 ).toString();
    }

    /**
     * Returns the value of the the second allele.
     *
     * @return String with the second allele
     */
    public String getAll2()
    {
        return getAllele ( 2 ).toString();
    }

    /**
     * Returns the two Allele objects.
     *
     * @return the two Allele objects.
     */
    public Allele[] getSortedAlleles()
    {
        return _allele;
    }

    /**
      * Returns the String containing the concatenation of the two alleles.
      * If the genotype is numeric then the string is "allele1" + ":" + "allele2"
      * else the string is "allele1" + "allele2".
      *
      * @return the concatenation String.
      */
    public String getConcatAlleles()
    {
        if ( this.isNumeric() )
            return _allele[0].toString() + ":" + _allele[1].toString();
        return _allele[0].toString() + _allele[1].toString();
    }

    /**
     * Returns the distance between two numeric PairAllele objects.
     *
     * @param a a PairAllele object.
     * @return the float value. Returns -1 if a PairAllele is not valid and numeric.
     */
    public float getDistance ( PairAllele a )
    {
        if ( isNumeric() && a.isNumeric()
                && (  isHomozigote() && a.isHomozigote()
                        ||   !isHomozigote() && !a.isHomozigote()  )
           )
        {
            Allele[] b= a.getSortedAlleles();
            final float d1 = _allele[0].getDistance ( b[1] );
            final float d2 = _allele[1].getDistance ( b[1] );
            if ( d1 > -1 && d2 > -1 )
                return d1 + d2;
        }
        return -1;
    }

    /**
     * Returns the distance between the two Alleles of a numeric PairAllele object.
     *
     * @return the float number. Returns -1 if PairAllele is not valid and numeric.
     */
    public float getAllelesDistance()
    {
        if ( isNumeric() )
            return _allele[0].getDistance ( _allele[1] );
        return -1;
    }

    /**
     * Overriding toString method.
     *
     * @return the string "allele1 - allele2".
     */
    public String toString()
    {
        return _allele[0].toString() + " - " + _allele[1].toString();
    }

    /**
     * Overriding equals method.
     * The method doesn't check for the inverse order of alleles.
     *
     * @param o the object to compare.
     * @return true or false.
     */
    public boolean equals ( Object o )
    {
        final Allele[] oth = ( ( PairAllele ) o ).getSortedAlleles();
        return ( _allele[0].equals ( oth[0] )
                && _allele[1].equals ( oth[1] ) );
    }

    /**
     * Overriding compareTo method.
     * The method doesn't check for the inverse order of alleles.
     *
     * @param aThat the object to compare.
     * @return an integer less, equal or greater than 0.
     */
    public int compareTo ( Object aThat )
    {
        final Allele[] a= new Allele[] { getAllele ( 1 ), getAllele ( 2 ) };
        final Allele[] b= new Allele[] { ( ( PairAllele ) aThat ).getAllele ( 1 ), ( ( PairAllele ) aThat ).getAllele ( 2 ) };
        Arrays.sort ( a );
        Arrays.sort ( b );
        if ( a[0].equals ( b[0] ) )
            return a[1].compareTo ( b[1] );
        else
            return a[0].compareTo ( b[0] );
    }

    /**
     * Overriding clone method.
     *
     * @return the new cloned object.
     */
    public Object clone()
    {
        PairAllele cloned =
            new PairAllele ( ( Allele ) getAllele ( 1 ).clone(), ( Allele ) getAllele ( 2 ).clone() );
        cloned.setMantainAllelesOrder ( isMantainAllelesOrder() );
        return ( Object ) cloned;
    }

} // end class

